#include "config.h"

#include <clx/libECM.h>
#include <clx/ecm_list.h>

#include "ms2sim.h"

#define USEC_ROUND	1000000

msl_sesslen_type_t	sesslen_type = MSL_SESSLEN_TYPE_FULL;
msl_istime_type_t	istime_type = MSL_ISTIME_TYPE_NONE;

static LIST_HEAD(sessions);

static ecm_uint32	n_sessions;
static ecm_uint32	n_dsessions;

static ecm_uint64	total_n_sessions;
static ecm_uint64	total_n_dsessions;
static ecm_uint64	total_n_completed_sessions;

static ecm_uint64	total_n_rounds;
static ecm_uint64	total_n_lost_rounds;

static ecm_uint32	sid_max = 1;

static msl_sesslen_t	msl_sesslen;
static msl_istime_t	msl_istime;

static void
setup_seektime(session_t *session, ecm_uint64 ts)
{
	media_t	*media = session->media;
	unsigned int	interseek_time;
	unsigned int	pos;
	int	dist;

	pos = session->usec_playing_pos / 1000000;
	if (msl_istime_get_seek(msl_istime, pos, media->length, &interseek_time, &dist) == 0) {
		session->ts_seek = 0;
		session->seekdist = 0;
	}
	else {
		session->ts_seek = ts + interseek_time * 1000000;
		session->seekdist = dist;
	}
}

session_t *
create_session(ecm_uint64 ts, ecm_uint32 mid)
{
	session_t	*session;

	session = (session_t *)ecm_malloc(sizeof(session_t));
	if (session == NULL) {
		ERR("session: out of memory");
		return NULL;
	}

	session->sid = sid_max++;

	session->ts = ts;
	session->state = STATE_IDLE;
	session->refcnt = 0;

	session->usec_playing_pos = 0;

	session->ts_deadline = 0;
	session->usec_round = USEC_ROUND;
	session->bytes_round = 0;
	session->bytes_buffered_client = 0;
	session->bytes_buffered = 0;
	session->offset_buff = 0;

	session->n_io_blks = 0;
	session->bcaches = NULL;

	session->bid = 1;

	session->n_rounds = 0;
	session->n_lost_rounds = 0;

	session->media = access_media(ts, mid);
	ECMASSERT(session->media != NULL);

	if (msl_sesslen)
		session->ts_sesslen = ts + msl_sesslen_get(msl_sesslen, session->media->length) * USEC_MAX;
	else
		session->ts_sesslen = 0;

	if (msl_istime)
		setup_seektime(session, ts);
	else
		session->ts_seek = 0;

	session->bytes_round = (ecm_uint32)(session->media->bitrate * (session->usec_round / 1000000.0));

	INIT_LIST_HEAD(&session->list);

	list_add(&session->list_accepted, &sessions);
	n_sessions++;

	DBG(DBG_SESSION, "create session: [%s]", dbg_session(session));

	return session;
}

void
free_session(session_t *session)
{
	DBG(DBG_SESSION, "free session: [%s]", dbg_session(session));

	ECMASSERT(session->refcnt == 0);
	list_del(&session->list_accepted);
	total_n_rounds += session->n_rounds;
	total_n_lost_rounds += session->n_lost_rounds;
	n_sessions--;
	ecm_free(session);
}

ecm_double
calc_loss_ratio(void)
{
	struct list_head	*lp;
	ecm_uint64	n_rounds = 0;
	ecm_uint64	n_lost_rounds = 0;
	ecm_double	avg_lost_ratio;
	session_t	*session;

	list_for_each (lp, &sessions) {
		session = list_entry(lp, session_t, list_accepted);
		n_rounds += session->n_rounds;
		n_lost_rounds += session->n_lost_rounds;
	}
	if (n_rounds < n_sessions * 100)
		return 0;
	avg_lost_ratio = (1.0 * n_lost_rounds) / n_rounds * 100;

	return avg_lost_ratio;
}

void
set_session_state(session_t *session, session_state_t state)
{
	switch (session->state) {
	case STATE_IDLE:
		break;
	case STATE_DISKIO:
		ECMASSERT(n_dsessions > 0);
		n_dsessions--;
		break;
	case STATE_LOADED:
		break;
	default:
		ECMASSERT(0);
		break;
	}

	switch (state) {
	case STATE_IDLE:
	case STATE_LOADED:
		break;
	case STATE_DISKIO:
		n_dsessions++;
		break;
	case STATE_DONE:
		total_n_completed_sessions++;
		notify_session_leave(session);
		break;
	default:
		ECMASSERT(0);
		break;
	}
	session->state = state;
}

void
try_to_seek_session(session_t *session, ecm_uint64 ts)
{
	media_t	*media = session->media;
	ecm_int32	bid_seek;

	if (session->ts_seek == 0 || ts < session->ts_seek)
		return;

	bid_seek = ((ecm_uint64)media->bitrate) * session->seekdist / cache_blksize;
	session->bid += bid_seek;
	if (session->bid <= 0)
		session->bid = 1;
	else if (session->bid > media->bid_max)
		session->bid = media->bid_max;

	//TODO: 
	session->usec_playing_pos += (session->seekdist * 1000000);

	setup_seektime(session, ts);
}

void
check_session_stats(ecm_uint64 ts)
{
	total_n_sessions += n_sessions;
	total_n_dsessions += n_dsessions;
}

static void
check_running_sessions(void)
{
	session_t	*session;
	struct list_head	*lp;
	ecm_uint64	n_rounds = 0;
	ecm_uint64	n_lost_rounds = 0;

	list_for_each (lp, &sessions) {
		session = list_entry(lp, session_t, list_accepted);
		n_rounds += session->n_rounds;
		n_lost_rounds += session->n_lost_rounds;
	}

	total_n_rounds += n_rounds;
	total_n_lost_rounds += n_lost_rounds;
}

void
report_session_result(void)
{
	ecm_double	avg_n_session;
	ecm_double	avg_n_dsession;

	ecm_double	avg_lost_ratio;

	check_running_sessions();

	avg_n_session = (1.0 * total_n_sessions) / stat_duration;
	avg_n_dsession = (1.0 * total_n_dsessions) / stat_duration;

	avg_lost_ratio = (1.0 * total_n_lost_rounds) / total_n_rounds * 100;

	printlog("Avg # of Session:            %.3lf\n", avg_n_session);
	printlog("Avg # of Disk I/O Session:   %.3lg\n", avg_n_dsession);
	printlog("Avg # of Completed Session:  %lu\n", total_n_completed_sessions);
	printlog("Avg Lost Ratio:              %.3lg%%\n", avg_lost_ratio);
}

ECMBOOL
init_session(void)
{
	if (sesslen_type != MSL_SESSLEN_TYPE_NONE &&
	    sesslen_type != MSL_SESSLEN_TYPE_FULL)
		msl_sesslen = msl_sesslen_create(sesslen_type, 0);

	if (istime_type != MSL_ISTIME_TYPE_NONE &&
	    istime_type != MSL_ISTIME_TYPE_NOSEEK)
		msl_istime = msl_istime_create(istime_type, 0);

	return ECM_TRUE;
}
